package broker

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/go-redis/redis/v8"
)

var (
	// ErrNotInitialized is returned when the broker is not initialized
	ErrNotInitialized = errors.New("broker is not initialized")

	// ErrInvalidRedisURL is returned when the Redis URL is invalid
	ErrInvalidRedisURL = errors.New("invalid Redis URL format, expected: redis://[username:password@]host:port/db")
)

// RedisBroker implements the Broker interface using Redis
type RedisBroker struct {
	client  *redis.Client
	ctx     context.Context
	msgChan chan []byte
	quit    chan struct{}
}

// NewRedisBroker creates a new Redis broker
func NewRedisBroker() *RedisBroker {
	return &RedisBroker{
		ctx:  context.Background(),
		quit: make(chan struct{}),
	}
}

// Initialize initializes the Redis broker with the given URL
// URL format: redis://[username:password@]host:port/db
func (rb *RedisBroker) Initialize(url string) error {
	// Parse Redis URL
	if !strings.HasPrefix(url, "redis://") {
		return ErrInvalidRedisURL
	}

	// Remove redis:// prefix
	url = strings.TrimPrefix(url, "redis://")

	// Extract auth info if present
	var auth string
	var hostPort string

	// Check if auth info is present (contains @)
	if idx := strings.LastIndex(url, "@"); idx != -1 {
		auth = url[:idx]
		hostPort = url[idx+1:]
	} else {
		hostPort = url
	}

	// Extract username and password from auth if present
	var username, password string
	if auth != "" {
		if idx := strings.Index(auth, ":"); idx != -1 {
			username = auth[:idx]
			password = auth[idx+1:]
		} else {
			// If no colon, treat the whole auth as the password
			password = auth
		}
	}

	// Parse host:port/db
	var db int
	if idx := strings.Index(hostPort, "/"); idx != -1 {
		// Try to parse db as int
		if hostPort[idx+1:] != "" {
			_, err := fmt.Sscanf(hostPort[idx+1:], "%d", &db)
			if err != nil {
				return err
			}
		}
		hostPort = hostPort[:idx]
	}

	// Create Redis client
	rb.client = redis.NewClient(&redis.Options{
		Addr:     hostPort,
		Username: username,
		Password: password,
		DB:       db,
	})

	// Test connection
	_, err := rb.client.Ping(rb.ctx).Result()
	return err
}

// Close closes the Redis broker connection
func (rb *RedisBroker) Close() error {
	if rb.client == nil {
		return ErrNotInitialized
	}

	close(rb.quit)

	return rb.client.Close()
}

// SendMessage sends a message to the specified queue
func (rb *RedisBroker) SendMessage(queue string, message []byte) error {
	if rb.client == nil {
		return ErrNotInitialized
	}

	return rb.client.RPush(rb.ctx, queue, message).Err()
}

// ConsumeMessages receives messages from the specified queue
func (rb *RedisBroker) ConsumeMessages(queue string) (<-chan []byte, error) {
	if rb.client == nil {
		return nil, ErrNotInitialized
	}

	rb.msgChan = make(chan []byte)

	// Start a goroutine to consume messages
	go func() {
		defer close(rb.msgChan)

		for {
			select {
			case <-rb.quit:
				return
			default:
				// Try to get a message with timeout
				result, err := rb.client.BLPop(rb.ctx, 1*time.Second, queue).Result()
				if err != nil {
					// Check if it's a timeout error (expected for BLPOP when no messages)
					if err == redis.Nil {
						continue
					}

					// Otherwise, this is a real error
					// In a real implementation, we would want to log this error
					continue
				}

				// BLPOP returns array with [queue, message]
				if len(result) < 2 {
					continue
				}

				// Send the message to the channel
				rb.msgChan <- []byte(result[1])
			}
		}
	}()

	return rb.msgChan, nil
}

// Ack acknowledges a message
// For Redis, this is a no-op as messages are removed from the queue when consumed
func (rb *RedisBroker) Ack(messageID string) error {
	return nil
}

// Reject rejects a message
// For Redis implementation, if requeue is true, we push the message back to the queue
func (rb *RedisBroker) Reject(messageID string, requeue bool) error {
	// For Redis this is a bit tricky because we don't store the message content with the ID
	// In a real implementation, we would need to keep track of message ID -> content mapping
	// or use a more sophisticated message format that includes the ID

	// This implementation is just a placeholder and doesn't actually do anything
	return nil
}
